#include "rpc/net/module/kqueue.h"

#ifdef __APPLE__

namespace {
uint16 FromEvent(uint16 event) {
  uint16 filter = 0;
  if (event & EV_READ) {
    filter |= EVFILT_READ;
  }
  if (event & EV_WRITE) {
    filter |= EVFILT_WRITE;
  }

  return filter;
}

uint16 ToEvent(uint16 filter) {
  uint16 event = 0;
  if (filter & EVFILT_READ) {
    event |= EV_READ;
  }
  if (filter & EVFILT_WRITE) {
    event |= EV_WRITE;
  }

  return event;
}

}

namespace rpc {

KQueue::KQueue()
    : kfd_(-1) {
}

KQueue::~KQueue() {
  if (kfd_ != -1) {
    ::close(kfd_);
  }
}

bool KQueue::Init() {
  if (kfd_ != -1) return true;
  kfd_ = ::kqueue();
  if (kfd_ == -1) {
    PLOG(WARNING)<< "kqueue error";
    return false;
  }

  struct kevent kev = { 0 };
  events_.resize(128, kev);

  return true;
}

bool KQueue::Add(Event* ev, int flag) {
  if (CheckExist(ev)) return false;
  if (!ChangeEvent(ev, FromEvent(flag), EV_ADD)) return false;

  ev->event = flag;

  return ev_set_.insert(ev->fd).second;
}

bool KQueue::Mod(Event* ev, int flag) {
  if (!CheckExist(ev)) return false;

  return !ChangeEvent(ev, FromEvent(flag), EV_ADD);
}

void KQueue::Del(Event* ev, int flag) {
  if (!CheckExist(ev)) return;
  if (ChangeEvent(ev, FromEvent(flag), EV_DELETE)) {
    ev_set_.erase(ev->fd);
  }
}

void KQueue::Loop() {
  loop_tid_ = ::pthread_self();
  while (!exit_) {
    if (ev_set_.empty()) {
      ::sleep(1);
      continue;
    }

    static timespec ts = { 1, 0 };
    int32 trigger = ::kevent(kfd_, NULL, 0, events_.data(), events_.size(),
                             &ts);
    if (trigger == -1) {
      if (errno != EINTR) {
        PLOG(WARNING)<< "kevent error";
      }
      continue;
    }

    for (int32 i = 0; i < trigger; ++i) {
      const struct kevent* kev = &events_[i];
      uint16 filter = ToEvent(kev->filter);

      Event* event = (Event*) kev->udata;
      event->cb(event->fd, event->arg, filter);
    }
  }
}

bool KQueue::ChangeEvent(Event* ev, uint16 filter, uint16 opt) const {
  struct kevent kev = { 0 };
  EV_SET(&kev, ev->fd, filter, opt, 0, 0, ev);
  int32 ret = ::kevent(kfd_, &kev, 1, NULL, 0, 0);
  if (ret != 0) {
    PLOG(WARNING)<< "kevent error";
    return false;
  }

  return true;
}

}

#ifdef __APPLE__

